#include "Buffer.h"

Buffer::Buffer(cantBytes maxCantidadBytes) {

	this->maxCantidadBytes = maxCantidadBytes;
	this->cantBytesActuales = 0;
}

Buffer::~Buffer() {

	list<ComponenteBuffer*>::iterator it = this->componentes.begin();

	for (; it != this->componentes.end(); it++) {
		delete (*it);
	}
}

void Buffer::escribir(const Bytes &bytes, offset off) {

	/*Primero veo con que Componentes tengo intersecciones*/
	offset offsetInicial = off;
	offset offsetFinal = off + bytes.getTamanio() - 1;

	bool interseccionEncontrada = false;

	list<ComponenteBuffer*>::iterator itComponentes = this->componentes.begin();
	list<ComponenteBuffer*> componentesIntersecciones;

	for ( ; itComponentes != this->componentes.end(); itComponentes++) {

		offset offsetInicialComponente = (*itComponentes)->getOffsetInicio();
		offset offsetFinalComponente = (*itComponentes)->getOffsetFinal();

		bool interseccionAux = this->calcularInserseccion(offsetInicial, offsetFinal, offsetInicialComponente, offsetFinalComponente);

		if (interseccionAux) {
			interseccionEncontrada = true;
			componentesIntersecciones.push_front((*itComponentes));
		}
	}

	if (interseccionEncontrada) {

		cantBytes cantBytesAcopiar = bytes.getTamanio();
		cantBytes cantBytesEscrita = 0;

		itComponentes = componentesIntersecciones.begin();
		ComponenteBuffer *componenteMenor = (*itComponentes);
		list<ComponenteBuffer*>::iterator itMenor = itComponentes;

		itComponentes++;

		offset offsetMinimo = componenteMenor->getOffsetInicio();
		offset offsetActual = off;

		while (cantBytesAcopiar) {

			/* Busco el componente de menor offset */
			for ( ; itComponentes != componentesIntersecciones.end(); itComponentes++) {

				offset offsetInicialComponente = (*itComponentes)->getOffsetInicio();

				if (offsetInicialComponente < offsetMinimo) {
					componenteMenor = (*itComponentes);
					itMenor = itComponentes;
					offsetMinimo = offsetInicialComponente;
				}
			}

			if (itMenor != componentesIntersecciones.end()) {
				componentesIntersecciones.erase(itMenor);
			}

			itComponentes = componentesIntersecciones.begin();
			itMenor = itComponentes;

			offsetMinimo = componenteMenor->getOffsetInicio();
			offset offsetMaximoDeComponenteMenor = componenteMenor->getOffsetFinal();
			/* En el caso de que el offset actual sea menor al offset minimo
			 * Debo crear un componente nuevo que tendra como offset minimo a offsetActual
			 * y como offset maximo a offset minimo -1 */

			if (offsetActual < offsetMinimo) {

				ComponenteBuffer* componente = new ComponenteBuffer(offsetActual);
				string bytesAux = bytes.toString().substr(cantBytesEscrita,offsetMinimo - offsetActual);

				cantBytesEscrita += bytesAux.length();
				componente->getBytesAlmacenados().agregar(bytesAux,0);

				this->cantBytesActuales += bytesAux.length();

				this->componentes.push_back(componente);

				cantBytesAcopiar -= bytesAux.length();
				offset offsetMaximoDeComponenteMenor = componenteMenor->getOffsetFinal();

				offsetActual = offsetMinimo;

				if (offsetFinal <= offsetMaximoDeComponenteMenor) {

					string bytesAux = bytes.toString().substr(cantBytesEscrita,offsetFinal - offsetMinimo + 1);
					cantBytesEscrita += bytesAux.length();

					offsetActual += bytesAux.length();
					componenteMenor->getBytesAlmacenados().reemplazar(bytesAux,0);

					cantBytesAcopiar = 0;
				} else {

					string bytesAux = bytes.toString().substr(cantBytesEscrita,offsetMaximoDeComponenteMenor - offsetMinimo + 1);
					cantBytesEscrita += bytesAux.length();

					offsetActual += bytesAux.length();

					componenteMenor->getBytesAlmacenados().reemplazar(bytesAux,0);

					cantBytesAcopiar -= bytesAux.length();

				}
			} else if ((offsetActual >= offsetMinimo) && (offsetActual <= offsetMaximoDeComponenteMenor)) {

				if (offsetFinal <= offsetMaximoDeComponenteMenor) {

					string bytesAux = bytes.toString().substr(cantBytesEscrita,offsetFinal - offsetActual + 1);
					cantBytesEscrita += bytesAux.length();

					componenteMenor->getBytesAlmacenados().reemplazar(bytesAux,offsetActual - offsetMinimo);

					cantBytesAcopiar = 0;

				} else {
					string bytesAux = bytes.toString().substr(cantBytesEscrita,offsetMaximoDeComponenteMenor - offsetActual + 1);

					cantBytesEscrita += bytesAux.length();

					componenteMenor->getBytesAlmacenados().reemplazar(bytesAux,offsetActual - offsetMinimo);

					cantBytesAcopiar -= bytesAux.length();

					offsetActual += bytesAux.length();
				}

			} else {

				string bytesAux = bytes.toString().substr(cantBytesEscrita,offsetFinal - cantBytesEscrita + 1);

				ComponenteBuffer* componenteNuevo = new ComponenteBuffer(offsetActual);
				componenteNuevo->getBytesAlmacenados().agregar(bytesAux,0);

				this->cantBytesActuales += bytesAux.length();
				this->componentes.push_back(componenteNuevo);

				cantBytesAcopiar = 0;
			}

			itComponentes = componentesIntersecciones.begin();
			if (itComponentes != componentesIntersecciones.end()) {
				componenteMenor = (*itComponentes);
				itMenor = itComponentes;
				offsetMinimo = componenteMenor->getOffsetInicio();
				offsetMaximoDeComponenteMenor = componenteMenor->getOffsetFinal();

				itComponentes++;
			}
		}


	} else {
		/* En caso de no haber interseccion creo un nuevo componente y lo agrego a la
		 * lista */
		ComponenteBuffer* componenteNuevo = new ComponenteBuffer(off);
		componenteNuevo->getBytesAlmacenados().agregar(bytes,0);

		this->cantBytesActuales += bytes.getTamanio();

		this->componentes.push_back(componenteNuevo);
	}
}

Bytes Buffer::leer(offset offsetInicial, cantBytes cantidad) {

	offset offsetFinal = offsetInicial + cantidad -1;
	bool interseccionEncontrada = false;

	list<ComponenteBuffer*>::iterator itComponentes = this->componentes.begin();
	list<ComponenteBuffer*> componentesIntersecciones;

	for ( ; itComponentes != this->componentes.end(); itComponentes++) {

		offset offsetInicialComponente = (*itComponentes)->getOffsetInicio();
		offset offsetFinalComponente = (*itComponentes)->getOffsetFinal();

		bool interseccionAux = this->calcularInserseccion(offsetInicial, offsetFinal, offsetInicialComponente, offsetFinalComponente);

		if (interseccionAux) {
			interseccionEncontrada = true;
			componentesIntersecciones.push_front((*itComponentes));
		}
	}


	if (interseccionEncontrada) {

		itComponentes = componentesIntersecciones.begin();
		ComponenteBuffer *componenteMenor = (*itComponentes);
		list<ComponenteBuffer*>::iterator itMenor = itComponentes;

		offset offsetMinimo = componenteMenor->getOffsetInicio();
		offset offsetActual = offsetInicial;

		Bytes aux("");
		string bytesLeidos;

		while (componentesIntersecciones.size() != 0) {


			itComponentes++;

			/* Busco el componente de menor offset */
			for ( ; itComponentes != componentesIntersecciones.end(); itComponentes++) {

				offset offsetInicialComponente = (*itComponentes)->getOffsetInicio();

				if (offsetInicialComponente < offsetMinimo) {
					componenteMenor = (*itComponentes);
					itMenor = itComponentes;
					offsetMinimo = offsetInicialComponente;
				}
			}

			if (itMenor != componentesIntersecciones.end()) {
				componentesIntersecciones.erase(itMenor);
			}

			if (offsetMinimo > offsetActual) {
				Bytes retorno("");

				return retorno;
			} else {
				offset offsetMaximoComponenteMenor = componenteMenor->getOffsetFinal();

				if (offsetFinal <= offsetMaximoComponenteMenor) {
					string bytesComponente = componenteMenor->getBytesAlmacenados().toString().substr(offsetActual - offsetMinimo,offsetFinal - offsetActual + 1);
					bytesLeidos += bytesComponente;
				} else {
					string bytesComponente = componenteMenor->getBytesAlmacenados().toString().substr(offsetActual - offsetMinimo,offsetMaximoComponenteMenor - offsetActual + 1);
					bytesLeidos += bytesComponente;
					offsetActual += offsetMaximoComponenteMenor - offsetActual + 1;
				}
			}

			if (componentesIntersecciones.size() != 0) {
				itComponentes = componentesIntersecciones.begin();
				itMenor = itComponentes;
				componenteMenor = (*itComponentes);
				offsetMinimo = componenteMenor->getOffsetInicio();
			}

		}
		if (bytesLeidos.length() == cantidad) {
			aux.agregar(bytesLeidos,0);
		}

		return aux;

	} else {
		Bytes aux("");

		return aux;
	}
}


void Buffer::liberarEspacio(cantBytes cantidadBytes) {

	cantBytes cantBytesLiberados = 0;

	if (this->componentes.size() != 0) {

		while (true) {
			ComponenteBuffer *componente = this->componentes.front();
			this->componentes.pop_front();
			cantBytesLiberados += componente->getBytesAlmacenados().getTamanio();

			delete componente;

			if ((cantBytesLiberados >= cantidadBytes) || (this->componentes.size() == 0)) {
				break;
			}
		}

	}

	this->cantBytesActuales -= cantBytesLiberados;
}


bool Buffer::calcularInserseccion(offset offsetInicialA , offset offsetFinalA, offset offsetInicialB, offset offsetFinalB) const {

	bool noHayInterseccion = false;
	/*Calculo los casos en los que no hay interseccion*/

	/* Caso 1: el offset final de B es menor al offset inicial de A */
	if (offsetFinalB < offsetInicialA) {
		noHayInterseccion = true;
	}

	/* Caso 2: el offset inicial de B es mayor al offset final de A */
	if (offsetInicialB > offsetFinalA) {
		noHayInterseccion = true;
	}

	return !noHayInterseccion;
}


void Buffer::imprimirContenido() {

	cout << endl << endl << "************* IMPRESION CONTENIDO BUFFER ***************" << endl << endl;

	if (this->componentes.size() == 0) {
		cout << "EL BUFFER NO CONTIENE BYTES ALMACENADOS" << endl;
		return;
	}

	list<ComponenteBuffer*> componentesAux = this->componentes;

	while (componentesAux.size() != 0) {

		list<ComponenteBuffer*>::iterator it = componentesAux.begin();
		list<ComponenteBuffer*>::iterator itMenor;

		itMenor = it;

		ComponenteBuffer* componenteMenor = (*it);
		it++;

		offset offsetMinimo = componenteMenor->getOffsetInicio();

		for ( ; it != componentesAux.end(); it++) {

			offset offsetInicialComponente = (*it)->getOffsetInicio();

			if (offsetInicialComponente < offsetMinimo) {
				componenteMenor = (*it);
				itMenor = it;
				offsetMinimo = offsetInicialComponente;
			}
		}

		if (itMenor != componentesAux.end()) {
			componentesAux.erase(itMenor);
		}

		cout << "[" << componenteMenor->getOffsetInicio() << ",";
		cout << componenteMenor->getOffsetFinal() << "] => ";
		cout << componenteMenor->getBytesAlmacenados().toString() << endl;
	}
	cout << endl << "*************  FIN IMPRESION CONTENIDO BUFFER **********" << endl << endl;
}

cantBytes Buffer::getDisponible() {

	return (this->maxCantidadBytes - this->cantBytesActuales);
}

void Buffer::eliminarOffsetSuperiores(offset offsetSuperior) {

	list<ComponenteBuffer*>::iterator it = this->componentes.begin();

	for ( ; it != this->componentes.end(); it++) {
		ComponenteBuffer* componente = (*it);

		if ((componente->getOffsetInicio() >= offsetSuperior)
				|| (componente->getOffsetFinal() >= offsetSuperior)) {

			it = this->componentes.erase(it);
			this->cantBytesActuales -= componente->getBytesAlmacenados().getTamanio();
			delete componente;
		}
	}
}
